package _effx.visual
{
   import flash.display.Bitmap;
   import flash.display.BitmapData;
   import flash.display.DisplayObjectContainer;
   import flash.geom.Matrix;
   import flash.geom.Point;

   /**
    * Create an animating PerlinNoise map with RGB channel. Example: </br>
    * var vf:PerlinField = new PerlinField(400, 300); </br>
    * vf.channels = 7; // 1 - Red, 2 - Green, 4 - Blue </br>
    * vf.numOctaves = 2; // counts of octaves. </br>
    * 
    * // render.
    * vf.update();
    * @author boy, cjboy1984@gmail.com
    */
   public class PerlinField
   {
      public var baseX:Number;
      public var baseY:Number;
      public var channels:uint; // 1 - Red, 2 - Green, 4 - Blue
      public var velocityPool:Vector.<Point>;
      private var _numOctaves:uint;
      
      private var _seed:uint;
      private var offsetPool:Array;
      private var _bmp:Bitmap;
      private var _scale:Number;
      // low resolution BitmapData, for better performance.
      private var _lowRezBmpData:BitmapData;
      
      // final PerlinNoise bitmapData.
      public var bitmapData:BitmapData;
      
      // --------------------- LINE ---------------------
      
      public function PerlinField(width:int, height:int)
      {
         _lowRezBmpData = new BitmapData(width >> 2, height >> 2);
         baseX = _lowRezBmpData.width >> 2;
         baseY = _lowRezBmpData.height >> 2;
         _scale = width / _lowRezBmpData.width;
         
         bitmapData = new BitmapData(_lowRezBmpData.width * _scale, _lowRezBmpData.height * _scale, true, 0x00000000);
         
         // default values
         channels = 3; // 1 - Red, 2 - Green, 4 - Blue
         numOctaves = 2;
         
         _seed = Math.random() * 10;
         _bmp = new Bitmap(bitmapData);
      }
      
      // ________________________________________________
      //                                    core function
      
      public function update():void
      {
         bitmapData.lock();
         
         for (var i:int = 0; i < offsetPool.length; ++i) 
         {
            offsetPool[i].x += velocityPool[i].x;
            offsetPool[i].y += velocityPool[i].y;
         }
         
         // because pelinNoise is a costly calculation rendering the effect on a scaled down bitmapData object then scaling it up after the calculation is over
         _lowRezBmpData.perlinNoise(baseX, baseY, numOctaves, _seed, true, true, channels, false, offsetPool);
         
         var matrix:Matrix = new Matrix();
         matrix.scale(_scale, _scale);
         bitmapData.draw(_lowRezBmpData, matrix, null, null, null, true);
         bitmapData.unlock();
      }
      
      public function dispose():void
      {
         if (_bmp && _bmp.parent)
         {
            _bmp.parent.removeChild(_bmp);
         }
         
         offsetPool = null;
         _bmp = null;
         _lowRezBmpData = null;
      }
      
      // ________________________________________________
      //                                             info
      
      /**
       * Number of octaves or individual noise functions to combine to create this noise. Larger numbers of octaves create images with greater detail, but require more processing time.
       */
      public function get numOctaves():uint { return _numOctaves; }
      public function set numOctaves(v:uint):void
      {
         _numOctaves = v;
         
         offsetPool = new Array();
         velocityPool = new Vector.<Point>();
         for (var i:int = 0; i < _numOctaves; ++i) 
         {
            offsetPool.push(new Point());
            
            var prefix:int = (i%2==0) ? 1 : -1;
            velocityPool.push(new Point(prefix * 1, 0));
         }
      }
      
      // ________________________________________________
      //                                            utils
      
      /**
       * Gives the amount of red color at given position. </br>
       * Value could be from 0 to 255.
       */
      public function getRed(xPos:int, yPos:int):uint
      {
         var c:uint = bitmapData.getPixel(xPos, yPos);
         return (( c >> 16 ) & 0xFF);
      }
      
      /**
       * Gives the amount of green color at given position. </br>
       * Value could be from 0 to 255.
       */
      public function getGreen(xPos:int, yPos:int):uint
      {
         var c:uint = bitmapData.getPixel(xPos, yPos);
         return ( (c >> 8) & 0xFF );
      }
      
      /**
       * Gives the amount of blue color at given position. </br>
       * Value could be from 0 to 255.
       */
      public function getBlue(xPos:int, yPos:int):uint
      {
         var c:uint = bitmapData.getPixel(xPos, yPos);
         return ( c & 0xFF );
      }
      
      /**
       * Converts given color channel amount to a velcity from -126 to 126. </br>
       */
      public function getVelcityFromColor(channelAmount:uint):Number
      {
         return ((channelAmount - 127) / 128);
      }
      
      /**
       * converts given vector to an angle in degrees. </br>
       */
      public function getAngleFromVelocity(xVel:Number, yVel:Number):Number
      {
         return ((Math.atan2(yVel, xVel)) / (Math.PI / 180));
      }
      
      // ################### protected ##################
      
      // #################### private ###################
      
   }
   
}